/*
 *  Copyright (C) 2004 Cidero, Inc.
 *
 *  Permission is hereby granted to any person obtaining a copy of 
 *  this software to use, copy, modify, merge, publish, and distribute
 *  the software for any non-commercial purpose, subject to the
 *  following conditions:
 *  
 *  The above copyright notice and this permission notice shall be included
 *  in all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
 *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
 *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY IN CONNECTION WITH THE SOFTWARE.
 * 
 *  File: $RCSfile: ShoutcastOutputStream.java,v $
 *
 */

package com.cidero.util;

import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.logging.Logger;


/**
 * Describe class <code>ShoutcastOutputStream</code> here.
 *
 * This class adds 'shoutcast' metadata support to a basic output stream.
 * Shoutcast is a slightly modified HTTP-GET protocol that is used by a 
 * number of Internet radio stations. The basics are:
 *
 *  1. When a client is interested in receiving shoutcast data in addition
 *     to the basic MP3 (or other format) data, the client includes a
 *     line in the HTTP request header like:
 *
 *       Icy-Metadata: 1
 *
 *  2. A shoutcast capable server sees the header, and enables the periodic
 *     insertion of shoutcast data. The HTTP response contains a header line
 *     like:
 *
 *       icy-metaint: 8192
 *
 *     which tells the client how often to expect a shoutcast data block
 *     (every 8192 bytes in this case)
 *
 *    Other optional headers (from ample.sourceforge.net) are:
 *
 *     icy-notice1   Informational message 
 *     icy-notice2   2nd informational message 
 *     icy-name      Name of the stream that server is sending.
 *                   (Station Name). 
 *
 *                   Note: Looks like Audiotron puts this momentarily on 
 *                   top line of it's screen at start of playback (then
 *                   switches to song/title
 *                   
 *     icy-genre     Genre of stream
 *     icy-url       Url associated for stream
 *                   (e.g. http://www.radioparadise.com)
 *     icy-pub       Not sure, believe it indicates if stream is public
 *                   or private 
 *     icy-br        Bit rate. This is for informational purposes, 
 *                   since most client decoders support VBR.
 *
 *     Also found references to (in sourceforge javashout code):
 * 
 *     icy-desc      Stream description
 *
 *
 *  3. The server then inserts shoutcast metadata every 8192 bytes. The
 *     first data byte is a byte count of the data to follow, divided by
 *     16. In most cases, no new data is pending, so only a single 0 byte
 *     is added.  When there is new data (typically a song title change), 
 *     a string like:
 *       
 *      StreamTitle='The White Stripes - Button To Button';
 *
 *     is added, and the byte count reflects the string length (rounded
 *     up to the next multiple of 16 bytes)
 *
 *  Note that the data/metadata sequence starts off with *data*, not metadata
 *
 *  That's it...simple!
 *
 */
public class ShoutcastOutputStream extends BufferedOutputStream
{
  private static Logger logger = Logger.getLogger("com.cidero.util");

  boolean      metadataModified = false;
  int          metadataInterval;
  int          bytesSentSinceLastMetadataBlock;
  long         totalBytes = 0;
  byte[]       metadataByteArray;
  byte[]       shoutcastBuf = new byte[4096];
  
  /**
   * Creates a new <code>ShoutcastOutputStream</code> instance.
   *
   */
  public ShoutcastOutputStream( OutputStream out, int metadataInterval )
  {
    super( out );
    
    this.metadataInterval = metadataInterval;
    bytesSentSinceLastMetadataBlock = 0;
  }
	
  public void setMetadata( String metadataString )
  {
    metadataByteArray = metadataString.getBytes();
    metadataModified = true;
  }

  public void write( byte[] buf, int off, int len ) throws IOException
  {
    int bytesRemaining = len;

    if( metadataInterval <= 0 ) // shoutcast data disabled?
    {
      super.write( buf, off, len );  
      totalBytes += len;
      return;
    }
    
    //System.out.println("write: off, len = " + off + " " + len );

    while( bytesRemaining > 0 )
    {
      // Compute space available before next metadata block
      int availSpace = metadataInterval - bytesSentSinceLastMetadataBlock;

      int writeBytes = bytesRemaining;
      if( writeBytes > availSpace )
        writeBytes = availSpace;

      if( writeBytes > 0 )
      {
        //System.out.println("write: off, len = " + off + " " + len );
        super.write( buf, (off + (len-bytesRemaining)), writeBytes );
        totalBytes += writeBytes;
        bytesSentSinceLastMetadataBlock += writeBytes;
      }

      bytesRemaining -= writeBytes;

      if( bytesRemaining <= 0 )
        break;
      
      //
      // Only wrote out part of the request since it's time to output
      // shoutcast block
      //
      if( metadataModified )
      {
        // Calculate length of metadata in 16-byte blocks (rounding up)
        int length16 = (metadataByteArray.length+15)/16;
            
        String metadataString = new String(metadataByteArray);
        
        //System.out.println("inserting metaData - len: " + length16*16 +
        //                     " data: [" + metadataString + "]" );

        if( length16 > 127 )
          logger.warning("HTTPServer: warning metaData > 2047 bytes");

        //
        // Pack metadata byte count and data in one block to avoid
        // overhead of multiple lower-level write() calls
        //
        shoutcastBuf[0] = (byte)length16;
        int n;
        for( n = 0; n < metadataByteArray.length ; n++ )
          shoutcastBuf[n+1] = metadataByteArray[n];

        // Zero pad the data block
        for( ; n < (length16*16) ; n++ ) 
          shoutcastBuf[n+1] = 0;
            
        super.write( shoutcastBuf, 0, (1 + length16*16) );
        metadataModified = false;
      }
      else
      {
        // Just write a single byte with a zero
        //System.out.println("writing shoutcast 0-byte block at byte " +
        //                           totalBytes );
        shoutcastBuf[0] = 0;
        super.write( shoutcastBuf, 0, 1 );        
      }

      bytesSentSinceLastMetadataBlock = 0;  // Reset
      
    }

  }

  
}

